#include "udpsocket.h"
#include <thread>
#include <set>
#include <list>
#include <vector>
#include <string.h>
#include <string>
#include <algorithm>

#ifndef __linux__
#   include <WinSock2.h>
#   include <WS2tcpip.h> //ip_mreq
#   include <iphlpapi.h>
#else
#   include <stdio.h>
#   include <sys/types.h>
#   include <sys/socket.h>
#   include <unistd.h>
#   include <arpa/inet.h>
#   include <ifaddrs.h>
#   define SOCKET int
#   define INVALID_SOCKET (-1)
#endif


namespace tool{ namespace net{

class UdpSenderPrivate
{
public:
    UdpSenderPrivate(const std::string& destAddr, uint16_t destPort,uint32_t localIp)
        :m_addr(destAddr)
        ,m_port(destPort)
    {
        INIT_WIN_SOCK;
        m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if(m_socket == INVALID_SOCKET)
        {
            DEBUGSOCK("socket");
            return;
        }
        int reuse = 1;
        NETOPTSET(m_socket,SOL_SOCKET,SO_REUSEADDR,reuse);

        m_sockaddr.sin_family = AF_INET;
        m_sockaddr.sin_port = htons(m_port);
        m_sockaddr.sin_addr.s_addr = inet_addr(m_addr.data());

        if(localIp != 0u){
            ip_mreq mreq;
            mreq.imr_multiaddr.s_addr = inet_addr(m_addr.data());
            mreq.imr_interface.s_addr = localIp;
            NETOPTSET(m_socket,IPPROTO_IP, IP_ADD_MEMBERSHIP, mreq)
        }

    }

    ~UdpSenderPrivate()
    {
        CLEAN_WIN_SOCK;
    #ifdef _WIN32
        closesocket(m_socket);
    #else
        close(m_socket);
    #endif
        m_socket = INVALID_SOCKET;
    }

    int sendData(const char *buff, const int len) const
    {
        if(m_socket == INVALID_SOCKET)
            return -1;
        int retval = sendto(m_socket, buff, len, 0, (sockaddr *)&m_sockaddr, sizeof(m_sockaddr));
        if(retval != len){
            DEBUGSOCK("sendto");
        }
        return retval;
    }

    const char* addr() const
    { return m_addr.data(); }

    uint16_t    port() const
    { return m_port; }

private:
    std::string m_addr;
    uint16_t    m_port;
    SOCKET      m_socket;
    sockaddr_in m_sockaddr;
};

UdpSender::UdpSender(const std::string& destAddr, uint16_t destPort,uint32_t localIp)
    :d(new UdpSenderPrivate(destAddr,destPort,localIp))
{ }

UdpSender::UdpSender(const NetAddress& na, uint32_t localIp)
    :d(new UdpSenderPrivate(TransAddr(na.ipValue()),na.port(),localIp))
{ }

UdpSender::~UdpSender()
{ delete d; }

void UdpSender::notifyForQuit()
{ }

int UdpSender::sendData(const char *buff, const int len) const
{ return d->sendData(buff,len); }

const char* UdpSender::addr() const
{ return d->addr(); }

uint16_t UdpSender::port() const
{ return d->port(); }


/*!
 * \brief The UdpReceiverPrivate class
 */
class UdpReceiverPrivate
{
public:
    UdpReceiverPrivate(DataListener pListener,uint32_t localIp, const std::string& groupAddr, uint16_t groupPort,int recBuffSize)
        :m_addr(groupAddr)
        ,m_port(groupPort)
        ,m_localIp(localIp)
        ,m_pListener(std::move(pListener))
        ,m_buffSize(recBuffSize)
        ,m_buffer(new char[recBuffSize])
    {
        INIT_WIN_SOCK;
        m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if(m_socket == INVALID_SOCKET)
        {
            DEBUGSOCK("socket");
            return;
        }

        int reuse = 1;
        NETOPTSET(m_socket,SOL_SOCKET,SO_REUSEADDR,reuse);

        m_sockaddr.sin_family = AF_INET;
        m_sockaddr.sin_port = htons(m_port);
        m_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        if(bind(m_socket, (sockaddr *)&m_sockaddr, sizeof(m_sockaddr)) < 0) {
            DEBUGSOCK("bind");
            return;
        }

        ip_mreq mreq;
        mreq.imr_multiaddr.s_addr = inet_addr(m_addr.data());
        mreq.imr_interface.s_addr = m_localIp;
        NETOPTSET(m_socket,IPPROTO_IP, IP_ADD_MEMBERSHIP, mreq)

        //NETOPTSET(m_socket,IPPROTO_IP,IP_MULTICAST_IF,m_localIp)

    #ifdef _WIN32
        int wait_time = 2000;
    #else
        timeval wait_time{2,0};
    #endif
        NETOPTSET(m_socket,SOL_SOCKET,SO_RCVTIMEO,wait_time)

//        bool bUsed = true;
//        NETOPTSET(m_socket,SOL_SOCKET,SO_DONTROUTE,bUsed);

        m_run = true;
        m_thread = std::thread(&UdpReceiverPrivate::recvThread,this);
    }

    ~UdpReceiverPrivate()
    {
        notifyForQuit();
        if(m_thread.joinable())
            m_thread.join();

//        ip_mreq mreq;
//        mreq.imr_multiaddr.s_addr = inet_addr(m_addr.data());
//        mreq.imr_interface.s_addr = m_localIp;
//        NETOPTSET(m_socket,IPPROTO_IP, IP_DROP_MEMBERSHIP, mreq);

    #ifdef _WIN32
        closesocket(m_socket);
    #else
        close(m_socket);
    #endif
        m_socket = INVALID_SOCKET;
        delete[] m_buffer;
        CLEAN_WIN_SOCK;
    }

    void notifyForQuit()
    { m_run = false; }

    const char* addr() const
    { return m_addr.data(); }

    uint16_t    port() const
    { return m_port; }
protected:
    void recvThread()
    {
        sockaddr_in sock_info;
        socklen_t size = sizeof(sock_info);
        while(m_run){
            int len = recvfrom(m_socket, m_buffer, m_buffSize, 0, (sockaddr *)&sock_info, &size);
            if(len == 0){
                continue;
            }
            if(len<0){
#ifdef _WIN32
                if(WSAGetLastError()!=WSAETIMEDOUT)
#else
                if(errno!=EAGAIN)

#endif
                    DEBUGSOCK("recvfrom");
            }else{
                //qDebug()<<"Sender Ip:"<<inet_ntoa(sock_info.sin_addr)<<"Port:"<<ntohs(sock_info.sin_port);
                m_pListener(m_buffer, len);
            }
        }
    }
private:
    std::string m_addr;
    uint16_t    m_port;
    uint32_t    m_localIp;
    SOCKET      m_socket;
    sockaddr_in m_sockaddr;
    bool        m_run;
    std::thread m_thread;
    DataListener    m_pListener;
    int         m_buffSize;
    char*       m_buffer;
};


UdpReceiver::UdpReceiver(DataListener pListener, uint32_t localIp, const std::string& groupAddr, uint16_t groupPort, int recBuffSize)
    :d( new UdpReceiverPrivate(pListener,localIp,groupAddr,groupPort,recBuffSize) )
{ }

UdpReceiver::UdpReceiver(DataListener pListener,uint32_t localIp,const NetAddress& na,int recBuffSize)
    :d( new UdpReceiverPrivate(pListener,localIp,na.ipString(),na.port(),recBuffSize) )
{ }

UdpReceiver::~UdpReceiver()
{ delete d; }

void UdpReceiver::notifyForQuit()
{ d->notifyForQuit(); }

const char* UdpReceiver::addr() const
{ return d->addr(); }

uint16_t UdpReceiver::port() const
{ return d->port(); }


//if use this function ,please link iphlpapi.lib
#if 0
std::set<uint32_t> localIpSet()
{
    std::set<uint32_t> iplst({0});//INADDR_ANY
#ifdef _WIN32
    IP_ADAPTER_INFO pAdp[16];
    ULONG adpSize = sizeof(pAdp);
    if(GetAdaptersInfo(pAdp,&adpSize) == ERROR_BUFFER_OVERFLOW){
       return iplst;
    }
    for(auto pHead = &pAdp[0];pHead;pHead = pHead->Next)
    {
        std::string name(pHead->Description);
        std::transform(name.begin(),name.end(),name.begin(),::tolower);
        if( (name.find("bluetooth")!=std::string::npos) ||
            (name.find("vmware")!=std::string::npos) ||
            (name.find("virtual")!=std::string::npos) )
            continue;

//        IP_ADAPTER_INFO& adp = *pHead;
//        std::cout<<">Net Name:"<<adp.AdapterName<<std::endl;
//        std::cout<<">Net Description:"<<adp.Description<<std::endl;
//        std::cout<<">Net Type:"<<adp.Type<<std::endl;MIB_IF_TYPE_ETHERNET;
//        std::cout<<">Net IP:"<<adp.IpAddressList.IpAddress.String<<std::endl;
//        std::cout<<">Net SubnetMast:"<<adp.IpAddressList.IpMask.String<<std::endl;
//        auto ptr = adp.Address;
//        printf(">Net Mac(%d): %02x:%02x:%02x:%02x:%02x:%02x\n",adp.AddressLength,ptr[0],ptr[1],ptr[2],ptr[3],ptr[4],ptr[5]);
//        std::cout<<""<<std::endl;

        iplst.insert(inet_addr(pHead->IpAddressList.IpAddress.String));
    }
#else
    ifaddrs *pList = nullptr;
    if(getifaddrs(&pList)){
        perror("get ifAddrs");
        return iplst;
    }
    for(auto p = pList;p!=nullptr;p = p->ifa_next)
    {
        if(p->ifa_addr->sa_family != AF_INET)
            continue;
        iplst.insert(reinterpret_cast<sockaddr_in*>(p->ifa_addr)->sin_addr.s_addr);
    }
    freeifaddrs(pList);
#endif
    const auto localLoopIp = inet_addr("127.0.0.1");
    const auto anyIp = INADDR_ANY;
    iplst.erase(localLoopIp);
    iplst.erase(anyIp);

    return iplst;
}
#endif

}}//namespace net::tool
